package vectors;
import java.util.Arrays;

//TODO: Make Vector implement Iterator<Double> if that works
public class Vector implements Location{
	
	private int nod;		// number of values
	private double[] values;	// All the values
	
	/**
	 * Sole Constructor. Takes a list of values
	 * @param values  the values for the vector
	 */
	public Vector(double[] values){
		this.values = values;
		this.nod = values.length;
	}
	
	/**
	 * A string representation of this vector
	 * 
	 * @return  the vector as: [v1][v2]...[vn]
	 */
	public String toString(){
		StringBuffer str = new StringBuffer();
		for(int i=0;i<getNOD();i++){
			str.append("[").append(getValue(i)).append("]");
		}
		return str.toString();
	}
	
	//#################### Mathematical Functions #######################
	
	/**
	 * Adds the given value to value in this vector
	 * 
	 * @param v  the value to add
	 * @return  the resulting vector
	 */
	public Vector plus(double v){
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] += v;
		}
		return new Vector(newValues);
	}
	
	/**
	 * The vector sum, adds each element to the corresponding element of the given vector.
	 * The given vector must be the same length as this one.
	 * 
	 * @param v  the vector to add to this one
	 * @throws IllegalArgumentException
	 * @return  the resulting vector
	 */
	public Vector plus(Vector v){
		if(v.getNOD() != nod){
			throw new IllegalArgumentException("Vectors must be the same length to do sum. Need: "+nod+", Got: "+v.getNOD());
		}
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] += v.getValue(i);
		}
		return new Vector(newValues);
	}
	
	/**
	 * The vector difference, subtracts each element by the corresponding element of the given vector.
	 * The given vector must be the same length as this one.
	 * 
	 * @param v  the vector to subtract by this one
	 * @throws IllegalArgumentException
	 * @return  the resulting vector
	 */
	public Vector minus(Vector v){
		return this.plus(v.times(-1));
	}
	
	/**
	 * Multiplies the given values to each value in this vector
	 * 
	 * @param v  the values to multiply into the vector
	 * @return  the resulting vector
	 */
	public Vector times(double v){
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] *= v;
		}
		return new Vector(newValues);
	}
	
	/**
	 * Multiplies each element in  this vector with the corresponding element in the given vector.
	 * IS NOT THE SAME AS DOT PRODUCT!!
	 * The given vector must be the same length as this one.
	 * 
	 * @param v  the vector to times into this one
	 * @throws IllegalArgumentException
	 * @return  the resulting vector
	 */
	public Vector times(Vector v){
		if(v.getNOD() != nod){
			throw new IllegalArgumentException("Vectors must be the same length to do multiplication. Need: "+nod+", Got: "+v.getNOD());
		}
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] *= v.getValue(i);
		}
		return new Vector(newValues);
	}
	
	/**
	 * The dot product between this vector and the given one.
	 * The given vector must be the same length as this one.
	 * 
	 * @param v  the vector to multiply with this one
	 * @throws IllegalArgumentException
	 * @return  the resulting vector
	 */
	public double dot(Vector v){
		if(v.getNOD() != nod){
			throw new IllegalArgumentException("Vectors must be the same length to do dot product. Need: "+nod+", Got: "+v.getNOD());
		}
		double total = 0.0;
		for(int i=0;i<nod;i++){
			total += v.getValue(i) * getValue(i);
		}
		return total;
	}
	
	/**
	 * Divides each value of this array by the given value.
	 * 
	 * @param v  the value to divide this vector by
	 * @return  the resulting vector
	 */
	public Vector divide(double v){
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] /= v;
		}
		return new Vector(newValues);
	}
	
	/**
	 * Divides each element in this vector by each corresponding element in the given vector.
	 * The given vector must be the same length as this one.
	 * 
	 * @param v  the vector that this vector will be divided by
	 * @throws IllegalArgumentException
	 * @return  the resulting vector
	 */
	public Vector divide(Vector v){
		if(v.getNOD() != nod){
			throw new IllegalArgumentException("Vectors must be the same length to do division. "+nod+", Got: "+v.getNOD());
		}
		double[] newValues = Arrays.copyOf(values, values.length);
		for(int i=0;i<nod;i++){
			newValues[i] /= v.getValue(i);
		}
		return new Vector(newValues);
	}
	
//############################ Regular Getters and Setters ###############################

	public int getNOD() {
		return nod;
	}

	public void setNOD(int nod) {
		this.nod = nod;
	}
	
	public double getValue(int i){
		return values[i];
	}
	
	public void setValue(int i, double value){
		values[i] = value;
	}
	
	//####################### Static Functions ##############################
	
	/**
	 * Creates random values for a vector between 0.0 and 1.0.
	 * 
	 * @param length  the number of values
	 * @return  the new list of values
	 */
	public static double[] randomVals(int length){
		double[] values = new double[length];
		for(int i=0;i<length;i++){
			values[i] = Math.random();
		}
		return values;
	}
	
	/**
	 * Creates a random vector with values between 0.0 and 1.0.
	 * 
	 * @param length  the number of values in the Vector
	 * @return  the new random vector
	 */
	public static Vector random(int length){
		return new Vector(Vector.randomVals(length));
	}
	
	/**
	 * Creates a new Vector of the given number of dimensions with all its values set to 0.0.
	 * 
	 * @param nod  the number of values in the vector
	 * @return  the new empty vector
	 */
	public static Vector zero(int nod){
		double[] values = new double[nod];
		for(int i=0;i<nod;i++){
			values[i] = 0.0;
		}
		return new Vector(values);
	}
	
	/**
	 * Creates a new unit Vector of the given number of dimensions with all its values set to 0.0, except in the given axis.
	 * 
	 * @param nod  the number of values in the vector
	 * @return  the new empty vector
	 */
	public static Vector unit(int nod, int axisIndex){
		double[] values = new double[nod];
		for(int i=0;i<nod;i++){
			if(i==axisIndex){
				values[i] = 1.0;
			} else {
				values[i] = 0.0;
			}
		}
		return new Vector(values);
	}

	/**
	 * The distance from the origin to the point denoted by the vector
	 * 
	 * @return the length
	 */
	public double getLength() {
		return Math.sqrt(getLengthSq());
	}

	/**
	 * The distance squared from the origin to the point denoted by the vector
	 * 
	 * @return the length
	 */
	public double getLengthSq() {
		double lengthSq = 0.0;
		for(int i=0;i<nod;i++){
			lengthSq += values[i]*values[i];
		}
		return lengthSq;
	}

	public Vector normalize() {
		double len = getLength();
		if(len==0.0){
			Vector up = Vector.zero(getNOD());
			up.setValue(0, 1);
			return up;
		} else {
			return divide(len);
		}
	}
	
	public Direction getDirection(){
		//TODO fill!
		return null;
	}
	
	public boolean equals(Object other){
		if(other instanceof Vector){
			Vector otherV = (Vector)other;
			for(int i=0;i<values.length;i++){
				if(values[i] != otherV.getValue(i)){
					return false;
				}
			}
			return true;
		} else {
			return false;
		}
	}
}
